feat: Add phishing-triage kit#182
Conversation
:robot_face: AgentKit Structural ValidationNew Contributions Detected
Check Results
🎉 All checks passed! This contribution follows the AgentKit structure. |
WalkthroughThis PR introduces a complete "Phishing Email Triage" kit for the Lamatic AgentKit repository: a flow definition, prompts, model config, constitution, and kit metadata for automated email triage, plus a Next.js analyst console web app with a server action calling the flow, a full shadcn/ui component library, styling, and documentation. ChangesPhishing Triage Kit
Analyst Console Web App
Suggested reviewers: Mission accepted. This message will self-destruct in five seconds—but the phishing triage kit will not: a full flow, prompts, guardrails, and a shadcn-powered console have been dropped, briefcase and all. Your task, should you choose to accept it, is to review a component library large enough to require its own safehouse. Good luck. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 21
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
kits/phishing-triage/apps/components/ui/use-toast.ts (1)
1-192: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winTwo agents, one identical playbook — consolidate the toast hook.
This file is an exact duplicate of
kits/phishing-triage/apps/hooks/use-toast.ts(same reducer, dispatch, listeners,toast()/useToast()). Having two independent copies of the same in-memory store risks state divergence (e.g., separatememoryState/listenerssingletons per module if both are imported by different consumers) and doubles future maintenance.♻️ Proposed fix
Keep one canonical implementation (e.g.,
hooks/use-toast.ts) and have the other simply re-export it:-'use client' - -// Inspired by react-hot-toast library -import * as React from 'react' - -... (full duplicated implementation) ... +export { useToast, toast, reducer } from '`@/hooks/use-toast`'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kits/phishing-triage/apps/components/ui/use-toast.ts` around lines 1 - 192, The toast hook logic in useToast, reducer, toast, and the module-level memoryState/listeners store is duplicated and can diverge if both copies are imported. Consolidate to a single canonical implementation (for example the existing hooks/use-toast.ts) and make this file re-export that module’s useToast and toast exports so all consumers share one in-memory store.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@kits/phishing-triage/.env.example`:
- Around line 5-7: The sample env placeholders in the dotenv example need
cleanup for dotenv-linter compliance: remove the unnecessary quotes from the
Lamatic credential values, and reorder the Lamatic entries so LAMATIC_API_KEY
comes before LAMATIC_API_URL while keeping PHISHING_TRIAGE quoted since its
placeholder contains spaces. Update the placeholder entries in the env sample
accordingly so the existing env-variable names remain clear and lint-friendly.
In `@kits/phishing-triage/apps/actions/orchestrate.ts`:
- Around line 47-55: The parsed flow response in orchestrate.ts is being cast to
Verdict without runtime shape checking, so malformed or partial payloads can
reach the UI. Add schema validation in the orchestrate flow after JSON.parse and
before returning success, using the Verdict contract from orchestrate.ts (or a
shared zod schema) to verify required fields like indicators, extracted_urls,
and verdict. If validation fails, throw a clear error instead of returning data
so page.tsx never receives an invalid verdict object.
- Line 44: The executeFlow call in orchestrate should be protected by an
application-level timeout because it can otherwise hang the server action;
update the flow execution path around lamaticClient.executeFlow in orchestrate
to enforce a hard timeout and fail fast when exceeded. If cancellation is
needed, route the request through a lower-level client path that supports
aborting, and keep the timeout handling localized to the flow call so the rest
of the action can recover cleanly.
In `@kits/phishing-triage/apps/app/layout.tsx`:
- Around line 6-7: The next/font instances in layout are created but never used,
so the optimized Geist fonts never reach the page. Update the App Router layout
component to apply the appropriate `variable` or `className` from `Geist` and
`Geist_Mono` on the `<html>` or `<body>` element, and adjust `globals.css` so
`--font-sans` references the next/font CSS variable instead of the literal Geist
family name. Use the `Geist`, `Geist_Mono`, and root layout markup to locate the
fix.
- Around line 15-26: RootLayout is missing the next-themes provider, so theme
state never reaches consumers like the Sonner toaster and the dark class won’t
be applied. Update RootLayout to wrap the rendered app in ThemeProvider, and add
suppressHydrationWarning to the html element; use the existing RootLayout
component in app/layout.tsx and keep Analytics inside the provider tree.
In `@kits/phishing-triage/apps/app/page.tsx`:
- Around line 58-66: The loading state in onAnalyze can get stuck if
analyzeEmail throws before reaching the final setLoading(false). Update the
onAnalyze handler in the page component to wrap the analyzeEmail call in a
try/finally block so loading is always cleared, while keeping the existing
success/error handling for the result and error state.
- Around line 34-38: The VERDICT_STYLES mapping uses a loose any type for the
icon field, which should be replaced with the correct Lucide icon component
type. Update the type annotation on VERDICT_STYLES in page.tsx to use a proper
icon component type that matches AlertTriangle, ShieldQuestion, and
CheckCircle2, so the contract stays explicit and type-safe without changing the
mapping values.
In `@kits/phishing-triage/apps/components/ui/carousel.tsx`:
- Around line 96-105: The cleanup in the Carousel effect is only unsubscribing
the `select` listener, leaving the `reInit` listener registered and causing
stale `onSelect` handlers to accumulate. Update the `React.useEffect` tied to
`api` and `onSelect` so the returned cleanup removes every listener added in the
effect, including both `api.on('reInit', onSelect)` and `api.on('select',
onSelect)`. Keep the subscription and unsubscription logic paired in the same
`useEffect` block to ensure Embla listeners are fully torn down on re-render or
unmount.
In `@kits/phishing-triage/apps/components/ui/chart.tsx`:
- Around line 72-103: The ChartStyle component is interpolating unvalidated
ChartConfig color/theme values directly into a raw style tag via
dangerouslySetInnerHTML, which creates an injection risk. Update ChartStyle to
validate or sanitize the resolved color before it is inserted into the CSS
string, and only emit safe CSS values from the config/theme lookup. Use the
existing ChartStyle, colorConfig, and THEMES flow to locate the fix, and keep
the dynamic style generation while ensuring any value coming from config cannot
break out of the CSS declaration.
In `@kits/phishing-triage/apps/components/ui/empty.tsx`:
- Around line 71-82: EmptyDescription is typed as React.ComponentProps<'p'> but
renders a div, so update the component to use the paragraph element
consistently. Keep the existing className/cn styling and props forwarding, but
change the rendered element inside EmptyDescription to match the p-based props
and semantics so consumers using EmptyDescription get the expected runtime
element.
In `@kits/phishing-triage/apps/components/ui/input-group.tsx`:
- Around line 1-9: The input-group component references React.ComponentProps in
several type definitions but never imports React, so add the React import at the
top of the file alongside the existing imports. Update the input-group.tsx
module that defines the input group, input, button, and addon component types so
the React namespace is available wherever ComponentProps is used.
In `@kits/phishing-triage/apps/components/ui/kbd.tsx`:
- Around line 18-26: KbdGroup currently declares div props but renders a kbd
element, so update the KbdGroup component to render a div instead and keep its
React.ComponentProps<'div'> contract aligned; preserve the existing data-slot,
className, and props passthrough behavior while changing only the outer element
in KbdGroup.
In `@kits/phishing-triage/apps/components/ui/sonner.tsx`:
- Around line 3-19: The Toaster component in sonner.tsx casts the inline style
to React.CSSProperties without importing React, so TypeScript cannot resolve
that namespace. Update the imports at the top of Toaster/Sonner component file
by adding a React import, or switch the cast to a directly imported
CSSProperties type, while keeping the existing Toaster and useTheme logic
unchanged.
In `@kits/phishing-triage/apps/components/ui/toaster.tsx`:
- Around line 1-35: The app currently has two separate toast implementations
exposing the same Toaster component name, which can cause duplicate
notifications or one to silently override the other. Consolidate on a single
toast system by choosing either the Radix-based Toaster here or the Sonner-based
one in sonner.tsx, then remove or rename the unused implementation and update
any layout or provider wiring so only one toast stack is mounted.
In `@kits/phishing-triage/apps/components/ui/use-mobile.tsx`:
- Around line 1-19: The useIsMobile hook is duplicated here and in the shared
hooks module, so keep only one source of truth to avoid divergence. Update the
ui/use-mobile.tsx module to re-export the existing useIsMobile implementation
from the shared hooks/use-mobile module, or remove this duplicate copy entirely,
and ensure sidebar.tsx continues using the shared hook reference.
In `@kits/phishing-triage/apps/components/ui/use-toast.ts`:
- Around line 171-182: The subscription in useToast is being torn down and
recreated on every toast update because the effect depends on state even though
only the stable setState callback is needed. Update the React.useEffect in
useToast to subscribe once on mount and unsubscribe on unmount by using an empty
dependency array (or otherwise removing state from the dependencies), while
keeping the listeners.push/remove logic intact.
- Around line 8-9: The toast cleanup delay in use-toast.ts is far too long,
causing dismissed toasts to remain in state for an excessive time. Update
TOAST_REMOVE_DELAY to a much shorter removal delay that matches the intended UX,
and keep the change localized to the toast state management around TOAST_LIMIT
and TOAST_REMOVE_DELAY so dismissed items are actually cleared promptly.
In `@kits/phishing-triage/apps/next.config.mjs`:
- Around line 8-10: Remove the TypeScript build override in the Next.js config
so build-time type errors are no longer ignored. Update the next.config.mjs
configuration around the typescript block to stop setting ignoreBuildErrors to
true, and ensure the app is cleaned up until next build succeeds with type
checking enabled.
In `@kits/phishing-triage/apps/orchestrate.ts`:
- Around line 10-12: Normalize the fallback values in AppConfig construction so
the config shape is consistent across endpoint, projectId, and apiKey. Update
the orchestration setup in orchestrate.ts where the config object is built so
all env-derived fields use the same null/empty-string convention, and keep the
AppConfig type aligned with that choice to avoid mixed defaults.
In `@kits/phishing-triage/apps/package.json`:
- Line 53: The package manifest currently uses a floating version for lamatic,
which makes installs non-reproducible. Update the dependencies in the
package.json for lamatic (and react-markdown as noted in the review) to fixed,
pinned versions instead of latest so fresh installs always resolve to the same
package versions.
In `@kits/phishing-triage/README.md`:
- Around line 1-5: The deploy anchor in the phishing-triage README opens a new
tab but is missing the required rel attributes, creating a tab-nabbing risk.
Update the external link that uses target="_blank" to include rel="noopener
noreferrer" so the Vercel deploy button remains safe when opened from the
README.
---
Outside diff comments:
In `@kits/phishing-triage/apps/components/ui/use-toast.ts`:
- Around line 1-192: The toast hook logic in useToast, reducer, toast, and the
module-level memoryState/listeners store is duplicated and can diverge if both
copies are imported. Consolidate to a single canonical implementation (for
example the existing hooks/use-toast.ts) and make this file re-export that
module’s useToast and toast exports so all consumers share one in-memory store.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7d0fcc46-818b-43fb-9d37-80d0debcc37c
📒 Files selected for processing (87)
kits/phishing-triage/.env.examplekits/phishing-triage/.gitignorekits/phishing-triage/README.mdkits/phishing-triage/agent.mdkits/phishing-triage/apps/.env.examplekits/phishing-triage/apps/.gitignorekits/phishing-triage/apps/.npmrckits/phishing-triage/apps/README.mdkits/phishing-triage/apps/actions/orchestrate.tskits/phishing-triage/apps/app/globals.csskits/phishing-triage/apps/app/layout.tsxkits/phishing-triage/apps/app/page.tsxkits/phishing-triage/apps/components.jsonkits/phishing-triage/apps/components/header.tsxkits/phishing-triage/apps/components/theme-provider.tsxkits/phishing-triage/apps/components/ui/accordion.tsxkits/phishing-triage/apps/components/ui/alert-dialog.tsxkits/phishing-triage/apps/components/ui/alert.tsxkits/phishing-triage/apps/components/ui/aspect-ratio.tsxkits/phishing-triage/apps/components/ui/avatar.tsxkits/phishing-triage/apps/components/ui/badge.tsxkits/phishing-triage/apps/components/ui/breadcrumb.tsxkits/phishing-triage/apps/components/ui/button-group.tsxkits/phishing-triage/apps/components/ui/button.tsxkits/phishing-triage/apps/components/ui/calendar.tsxkits/phishing-triage/apps/components/ui/card.tsxkits/phishing-triage/apps/components/ui/carousel.tsxkits/phishing-triage/apps/components/ui/chart.tsxkits/phishing-triage/apps/components/ui/checkbox.tsxkits/phishing-triage/apps/components/ui/collapsible.tsxkits/phishing-triage/apps/components/ui/command.tsxkits/phishing-triage/apps/components/ui/context-menu.tsxkits/phishing-triage/apps/components/ui/dialog.tsxkits/phishing-triage/apps/components/ui/drawer.tsxkits/phishing-triage/apps/components/ui/dropdown-menu.tsxkits/phishing-triage/apps/components/ui/empty.tsxkits/phishing-triage/apps/components/ui/field.tsxkits/phishing-triage/apps/components/ui/form.tsxkits/phishing-triage/apps/components/ui/hover-card.tsxkits/phishing-triage/apps/components/ui/input-group.tsxkits/phishing-triage/apps/components/ui/input-otp.tsxkits/phishing-triage/apps/components/ui/input.tsxkits/phishing-triage/apps/components/ui/item.tsxkits/phishing-triage/apps/components/ui/kbd.tsxkits/phishing-triage/apps/components/ui/label.tsxkits/phishing-triage/apps/components/ui/menubar.tsxkits/phishing-triage/apps/components/ui/navigation-menu.tsxkits/phishing-triage/apps/components/ui/pagination.tsxkits/phishing-triage/apps/components/ui/popover.tsxkits/phishing-triage/apps/components/ui/progress.tsxkits/phishing-triage/apps/components/ui/radio-group.tsxkits/phishing-triage/apps/components/ui/resizable.tsxkits/phishing-triage/apps/components/ui/scroll-area.tsxkits/phishing-triage/apps/components/ui/select.tsxkits/phishing-triage/apps/components/ui/separator.tsxkits/phishing-triage/apps/components/ui/sheet.tsxkits/phishing-triage/apps/components/ui/sidebar.tsxkits/phishing-triage/apps/components/ui/skeleton.tsxkits/phishing-triage/apps/components/ui/slider.tsxkits/phishing-triage/apps/components/ui/sonner.tsxkits/phishing-triage/apps/components/ui/spinner.tsxkits/phishing-triage/apps/components/ui/switch.tsxkits/phishing-triage/apps/components/ui/table.tsxkits/phishing-triage/apps/components/ui/tabs.tsxkits/phishing-triage/apps/components/ui/textarea.tsxkits/phishing-triage/apps/components/ui/toast.tsxkits/phishing-triage/apps/components/ui/toaster.tsxkits/phishing-triage/apps/components/ui/toggle-group.tsxkits/phishing-triage/apps/components/ui/toggle.tsxkits/phishing-triage/apps/components/ui/tooltip.tsxkits/phishing-triage/apps/components/ui/use-mobile.tsxkits/phishing-triage/apps/components/ui/use-toast.tskits/phishing-triage/apps/hooks/use-mobile.tskits/phishing-triage/apps/hooks/use-toast.tskits/phishing-triage/apps/lib/lamatic-client.tskits/phishing-triage/apps/lib/utils.tskits/phishing-triage/apps/next.config.mjskits/phishing-triage/apps/orchestrate.tskits/phishing-triage/apps/package.jsonkits/phishing-triage/apps/postcss.config.mjskits/phishing-triage/apps/tsconfig.jsonkits/phishing-triage/constitutions/default.mdkits/phishing-triage/flows/phishing-triage.tskits/phishing-triage/lamatic.config.tskits/phishing-triage/model-configs/phishing-triage_analyze.tskits/phishing-triage/prompts/phishing-triage_analyze_system.mdkits/phishing-triage/prompts/phishing-triage_analyze_user.md
| LAMATIC_API_URL="LAMATIC_API_URL" | ||
| LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID" | ||
| LAMATIC_API_KEY="LAMATIC_API_KEY" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clean up the sample env values for dotenv-linter.
The Lamatic credential placeholders do not need quotes, and LAMATIC_API_KEY should come before LAMATIC_API_URL if the repo keeps enforcing dotenv-linter. PHISHING_TRIAGE can stay quoted because its placeholder contains spaces.
♻️ Proposed fix
# Phishing Triage flow ID (from Lamatic Studio → your deployed flow)
PHISHING_TRIAGE="PHISHING_TRIAGE Flow ID"
# Lamatic project credentials (Studio → Settings)
-LAMATIC_API_URL="LAMATIC_API_URL"
-LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID"
-LAMATIC_API_KEY="LAMATIC_API_KEY"
+LAMATIC_API_KEY=LAMATIC_API_KEY
+LAMATIC_API_URL=LAMATIC_API_URL
+LAMATIC_PROJECT_ID=LAMATIC_PROJECT_ID📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| LAMATIC_API_URL="LAMATIC_API_URL" | |
| LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID" | |
| LAMATIC_API_KEY="LAMATIC_API_KEY" | |
| LAMATIC_API_KEY=LAMATIC_API_KEY | |
| LAMATIC_API_URL=LAMATIC_API_URL | |
| LAMATIC_PROJECT_ID=LAMATIC_PROJECT_ID |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 5-5: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 6-6: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 7-7: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 7-7: [UnorderedKey] The LAMATIC_API_KEY key should go before the LAMATIC_API_URL key
(UnorderedKey)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/.env.example` around lines 5 - 7, The sample env
placeholders in the dotenv example need cleanup for dotenv-linter compliance:
remove the unnecessary quotes from the Lamatic credential values, and reorder
the Lamatic entries so LAMATIC_API_KEY comes before LAMATIC_API_URL while
keeping PHISHING_TRIAGE quoted since its placeholder contains spaces. Update the
placeholder entries in the env sample accordingly so the existing env-variable
names remain clear and lint-friendly.
Source: Linters/SAST tools
| body: email.body, | ||
| } | ||
|
|
||
| const resData: any = await lamaticClient.executeFlow(flow.workflowId, inputs) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== orchestrate file ==\n'
cat -n kits/phishing-triage/apps/actions/orchestrate.ts | sed -n '1,220p'
printf '\n== search for lamaticClient / executeFlow ==\n'
rg -n "executeFlow|lamaticClient|lamatic" kits/phishing-triage/apps -S
printf '\n== search repository for SDK types/docs ==\n'
rg -n "executeFlow\\(" -S .
rg -n "AbortController|signal|timeout" -S kits/phishing-triage/apps .Repository: Lamatic/AgentKit
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== phishing-triage lamatic client ==\n'
cat -n kits/phishing-triage/apps/lib/lamatic-client.ts | sed -n '1,220p'
printf '\n== weekly-routine-coach timeout wrapper reference ==\n'
cat -n kits/weekly-routine-coach/apps/lib/lamatic-client.ts | sed -n '1,180p'
printf '\n== lamatic package version pins ==\n'
rg -n '"lamatic"|lamatic@' kits/*/apps/package.json kits/*/apps/package-lock.json kits/*/apps/pnpm-lock.yaml -S
printf '\n== search for executeFlow type declarations in repo ==\n'
rg -n "executeFlow\\s*\\(" kits -g '!**/node_modules/**' -SRepository: Lamatic/AgentKit
Length of output: 14219
🌐 Web query:
Lamatic SDK executeFlow AbortController signal timeout options lamatic package documentation
💡 Result:
The official Lamatic SDK documentation for executeFlow does not natively support AbortController or specific timeout signal options directly within the method signature [1][2][3][4]. The executeFlow(flowId: string, payload: Object) method signature is designed to execute a workflow and return a promise [1][2]. It does not accept additional configuration objects for request cancellation or timeout control [2]. If you need to implement timeouts or cancellation, you must handle these patterns at the application level rather than through the SDK method arguments: 1. Request Timeouts: Because the SDK does not provide a signal parameter, you can manage timeouts by wrapping the executeFlow call in a promise race or by implementing custom polling logic [1]. The SDK documentation suggests that for longer-running workflows, you may need to use status-checking methods (like checkStatus) rather than relying on the initial executeFlow promise to resolve within a specific window [1]. 2. AbortController: Since executeFlow does not accept an AbortSignal, it cannot be natively cancelled using the standard Web API AbortController [2]. If your environment allows it, you might be able to implement cancellation by using a lower-level HTTP client (like axios or fetch) directly to send the GraphQL request, which would allow you to pass an AbortSignal to the underlying request [5][6]. For best practices, always wrap executeFlow in try-catch blocks, as the SDK throws exceptions for configuration, network, or parsing issues [1][2]. If you encounter a timeout, the documentation notes that the request may still be executing in the background, and you should check the status again using the request ID [1].
Citations:
- 1: https://lamatic.ai/docs/api-integration/sdk
- 2: https://github.com/Lamatic/lamatic-sdk
- 3: https://lamatic.ai/docs/api-integration/sdk/react
- 4: https://lamatic.ai/docs/sdk/react
- 5: https://lamatic.ai/docs/api-integration/integration-guide
- 6: https://lamatic.ai/docs/sdk/integration-guide
Bound this flow call with an application-level timeout. lamaticClient.executeFlow doesn’t accept AbortSignal/timeout options, so a slow or hung flow can still stall this server action. Wrap it with a timeout guard, or switch to a lower-level request path if cancellation is required.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/actions/orchestrate.ts` at line 44, The executeFlow
call in orchestrate should be protected by an application-level timeout because
it can otherwise hang the server action; update the flow execution path around
lamaticClient.executeFlow in orchestrate to enforce a hard timeout and fail fast
when exceeded. If cancellation is needed, route the request through a
lower-level client path that supports aborting, and keep the timeout handling
localized to the flow call so the rest of the action can recover cleanly.
| const answer = resData?.result?.answer ?? resData?.result?.output ?? resData?.answer | ||
|
|
||
| if (!answer) { | ||
| throw new Error("No verdict was returned by the flow.") | ||
| } | ||
|
|
||
| const parsed: Verdict = typeof answer === "string" ? JSON.parse(answer) : answer | ||
|
|
||
| return { success: true, data: parsed } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Verdict is trusted blind — no schema validation before it ships to the UI.
Verdict declares indicators, extracted_urls, verdict, etc. as required, non-optional fields, but the parsed answer (LLM/flow output, possibly a raw JSON string) is cast to Verdict with zero runtime validation. Confirmed by the downstream consumer (kits/phishing-triage/apps/app/page.tsx) defensively using result.indicators?.length > 0 and result.extracted_urls?.length > 0 — optional chaining on fields the type promises are always present. If the flow returns a malformed/partial payload, JSON.parse succeeds but the shape mismatch surfaces as silent UI breakage (e.g. VERDICT_STYLES[result.verdict] returning undefined for an unexpected verdict string) rather than a clear error to the analyst.
🛡️ Proposed fix: validate with zod before returning success
+const VerdictSchema = z.object({
+ verdict: z.enum(["phishing", "suspicious", "legitimate"]),
+ confidence: z.number(),
+ risk_score: z.number(),
+ indicators: z.array(z.string()),
+ extracted_urls: z.array(z.string()),
+ recommended_action: z.string(),
+ reasoning: z.string(),
+ iocs: z.record(z.unknown()).optional(),
+})
+
- const parsed: Verdict = typeof answer === "string" ? JSON.parse(answer) : answer
+ const rawParsed = typeof answer === "string" ? JSON.parse(answer) : answer
+ const parsed: Verdict = VerdictSchema.parse(rawParsed)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const answer = resData?.result?.answer ?? resData?.result?.output ?? resData?.answer | |
| if (!answer) { | |
| throw new Error("No verdict was returned by the flow.") | |
| } | |
| const parsed: Verdict = typeof answer === "string" ? JSON.parse(answer) : answer | |
| return { success: true, data: parsed } | |
| const VerdictSchema = z.object({ | |
| verdict: z.enum(["phishing", "suspicious", "legitimate"]), | |
| confidence: z.number(), | |
| risk_score: z.number(), | |
| indicators: z.array(z.string()), | |
| extracted_urls: z.array(z.string()), | |
| recommended_action: z.string(), | |
| reasoning: z.string(), | |
| iocs: z.record(z.unknown()).optional(), | |
| }) | |
| const answer = resData?.result?.answer ?? resData?.result?.output ?? resData?.answer | |
| if (!answer) { | |
| throw new Error("No verdict was returned by the flow.") | |
| } | |
| const rawParsed = typeof answer === "string" ? JSON.parse(answer) : answer | |
| const parsed: Verdict = VerdictSchema.parse(rawParsed) | |
| return { success: true, data: parsed } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/actions/orchestrate.ts` around lines 47 - 55, The
parsed flow response in orchestrate.ts is being cast to Verdict without runtime
shape checking, so malformed or partial payloads can reach the UI. Add schema
validation in the orchestrate flow after JSON.parse and before returning
success, using the Verdict contract from orchestrate.ts (or a shared zod schema)
to verify required fields like indicators, extracted_urls, and verdict. If
validation fails, throw a clear error instead of returning data so page.tsx
never receives an invalid verdict object.
| const _geist = Geist({ subsets: ["latin"] }) | ||
| const _geistMono = Geist_Mono({ subsets: ["latin"] }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Font agents never actually deployed to the field.
Geist/Geist_Mono (Line 6-7) are instantiated but their .variable/.className output is never applied to <html> or <body> (Line 20 just uses the static font-sans utility). Since globals.css hardcodes --font-sans: 'Geist', 'Geist Fallback' as a literal family name rather than referencing the next/font CSS variable, the optimized/self-hosted font never loads — you get whatever generic font the browser resolves for the literal string "Geist" (likely nothing, falling to system sans).
🔤 Suggested fix
-const _geist = Geist({ subsets: ["latin"] })
-const _geistMono = Geist_Mono({ subsets: ["latin"] })
+const geist = Geist({ subsets: ["latin"], variable: "--font-geist-sans" })
+const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" })
...
- <body className="font-sans antialiased">
+ <body className={`${geist.variable} ${geistMono.variable} font-sans antialiased`}>And update globals.css to reference var(--font-geist-sans) instead of the literal 'Geist' family name.
Also applies to: 20-20
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/app/layout.tsx` around lines 6 - 7, The next/font
instances in layout are created but never used, so the optimized Geist fonts
never reach the page. Update the App Router layout component to apply the
appropriate `variable` or `className` from `Geist` and `Geist_Mono` on the
`<html>` or `<body>` element, and adjust `globals.css` so `--font-sans`
references the next/font CSS variable instead of the literal Geist family name.
Use the `Geist`, `Geist_Mono`, and root layout markup to locate the fix.
| export default function RootLayout({ | ||
| children, | ||
| }: Readonly<{ children: React.ReactNode }>) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body className="font-sans antialiased"> | ||
| {children} | ||
| <Analytics /> | ||
| </body> | ||
| </html> | ||
| ) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== layout ==\n'
cat -n kits/phishing-triage/apps/app/layout.tsx
printf '\n== theme-provider ==\n'
cat -n kits/phishing-triage/apps/components/theme-provider.tsx
printf '\n== globals.css (first 260 lines) ==\n'
sed -n '1,260p' kits/phishing-triage/apps/app/globals.css
printf '\n== search theme-provider / next-themes usage ==\n'
rg -n "ThemeProvider|next-themes|suppressHydrationWarning|class=\"dark\"|attribute=\"class\"" kits/phishing-triage/apps -g '!**/node_modules/**'Repository: Lamatic/AgentKit
Length of output: 6476
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== search for theme controls/usages ==\n'
rg -n "setTheme\(|useTheme\(|ThemeProvider|next-themes|dark mode|toggle theme|theme=" kits/phishing-triage/apps -g '!**/node_modules/**'
printf '\n== sonner ==\n'
cat -n kits/phishing-triage/apps/components/ui/sonner.tsxRepository: Lamatic/AgentKit
Length of output: 1699
Mount ThemeProvider in RootLayout. next-themes is installed, but the app never renders its provider, so the .dark class never gets applied and theme consumers like the Sonner toaster stay disconnected. Wrap the layout in ThemeProvider and add suppressHydrationWarning to <html>.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/app/layout.tsx` around lines 15 - 26, RootLayout is
missing the next-themes provider, so theme state never reaches consumers like
the Sonner toaster and the dark class won’t be applied. Update RootLayout to
wrap the rendered app in ThemeProvider, and add suppressHydrationWarning to the
html element; use the existing RootLayout component in app/layout.tsx and keep
Analytics inside the provider tree.
| function useToast() { | ||
| const [state, setState] = React.useState<State>(memoryState) | ||
|
|
||
| React.useEffect(() => { | ||
| listeners.push(setState) | ||
| return () => { | ||
| const index = listeners.indexOf(setState) | ||
| if (index > -1) { | ||
| listeners.splice(index, 1) | ||
| } | ||
| } | ||
| }, [state]) |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
Self-destructing subscription, again and again.
The effect's dependency array [state] re-runs the subscribe/unsubscribe cleanup on every toast dispatch (since state changes each time), even though setState is a stable identity. This churns the shared listeners array unnecessarily on every state update.
♻️ Proposed fix
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
- }, [state])
+ }, [])📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function useToast() { | |
| const [state, setState] = React.useState<State>(memoryState) | |
| React.useEffect(() => { | |
| listeners.push(setState) | |
| return () => { | |
| const index = listeners.indexOf(setState) | |
| if (index > -1) { | |
| listeners.splice(index, 1) | |
| } | |
| } | |
| }, [state]) | |
| function useToast() { | |
| const [state, setState] = React.useState<State>(memoryState) | |
| React.useEffect(() => { | |
| listeners.push(setState) | |
| return () => { | |
| const index = listeners.indexOf(setState) | |
| if (index > -1) { | |
| listeners.splice(index, 1) | |
| } | |
| } | |
| }, []) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/components/ui/use-toast.ts` around lines 171 - 182,
The subscription in useToast is being torn down and recreated on every toast
update because the effect depends on state even though only the stable setState
callback is needed. Update the React.useEffect in useToast to subscribe once on
mount and unsubscribe on unmount by using an empty dependency array (or
otherwise removing state from the dependencies), while keeping the
listeners.push/remove logic intact.
| typescript: { | ||
| ignoreBuildErrors: true, | ||
| }, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Your mission, should you choose to accept it: stop ignoring TypeScript build errors.
ignoreBuildErrors: true lets type errors silently ship to production in a tool used to triage phishing threats — exactly the kind of console where a subtle bug shouldn't slip past the compiler. Recommend removing this once the app's type errors (if any) are resolved, so next build fails loudly instead of quietly.
🕵️ Proposed fix
const nextConfig = {
- typescript: {
- ignoreBuildErrors: true,
- },
images: {
unoptimized: true,
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| typescript: { | |
| ignoreBuildErrors: true, | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/next.config.mjs` around lines 8 - 10, Remove the
TypeScript build override in the Next.js config so build-time type errors are no
longer ignored. Update the next.config.mjs configuration around the typescript
block to stop setting ignoreBuildErrors to true, and ensure the app is cleaned
up until next build succeeds with type checking enabled.
| endpoint: process.env.LAMATIC_API_URL ?? "", | ||
| projectId: process.env.LAMATIC_PROJECT_ID ?? null, | ||
| apiKey: process.env.LAMATIC_API_KEY ?? "", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Inconsistent env-var fallback defaults.
endpoint/apiKey default to "" while projectId defaults to null — a minor type inconsistency in AppConfig. Low impact since lamatic-client.ts validates directly against process.env, but worth normalizing for consistency.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/orchestrate.ts` around lines 10 - 12, Normalize the
fallback values in AppConfig construction so the config shape is consistent
across endpoint, projectId, and apiKey. Update the orchestration setup in
orchestrate.ts where the config object is built so all env-derived fields use
the same null/empty-string convention, and keep the AppConfig type aligned with
that choice to avoid mixed defaults.
| "date-fns": "4.1.0", | ||
| "embla-carousel-react": "8.5.1", | ||
| "input-otp": "1.4.1", | ||
| "lamatic": "latest", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the latest published version of the npm package "lamatic"?
💡 Result:
The latest version of the npm package lamatic is not explicitly stated in the provided search results. While official documentation and the associated GitHub repository confirm the existence of the lamatic package and provide instructions for its installation via npm install lamatic [1][2][3], these sources do not display the specific version number. Users can determine the current latest version by running the following command in their terminal: npm view lamatic version
Citations:
- 1: https://lamatic.ai/docs/api-integration/sdk
- 2: https://github.com/Lamatic/lamatic-sdk
- 3: https://lamatic.ai/docs/get-started
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant package manifest and surrounding context.
sed -n '1,140p' kits/phishing-triage/apps/package.json | cat -nRepository: Lamatic/AgentKit
Length of output: 3164
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether the manifest still uses "latest" for the flagged dependencies.
python3 - <<'PY'
import json
from pathlib import Path
path = Path("kits/phishing-triage/apps/package.json")
data = json.loads(path.read_text())
deps = data.get("dependencies", {})
for name in ["lamatic", "react-markdown"]:
print(f"{name}: {deps.get(name)!r}")
PYRepository: Lamatic/AgentKit
Length of output: 197
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find any references to the kit-only layout requirements in the repo for context.
rg -n "apps/package.json|next.config.mjs|tsconfig.json|\.env\.example|kits/" -S .Repository: Lamatic/AgentKit
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check for lockfiles in the kit app directory and inspect the app metadata.
fd -a 'package-lock.json|pnpm-lock.yaml|yarn.lock' kits/phishing-triage/apps kits/phishing-triage -t fRepository: Lamatic/AgentKit
Length of output: 154
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [
Path("kits/phishing-triage/apps/package-lock.json"),
Path("kits/phishing-triage/apps/pnpm-lock.yaml"),
Path("kits/phishing-triage/apps/yarn.lock"),
Path("kits/phishing-triage/package-lock.json"),
]:
print(f"{p}: {'exists' if p.exists() else 'missing'}")
PYRepository: Lamatic/AgentKit
Length of output: 350
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect whether the kit app ships a lockfile near package.json.
find kits/phishing-triage -maxdepth 2 \( -name 'package-lock.json' -o -name 'pnpm-lock.yaml' -o -name 'yarn.lock' \) -printRepository: Lamatic/AgentKit
Length of output: 154
Pin these dependencies instead of latest
lamatic and react-markdown will float to whatever npm publishes next. With no lockfile in this kit, fresh installs can pull breaking changes and make builds non-reproducible.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/apps/package.json` at line 53, The package manifest
currently uses a floating version for lamatic, which makes installs
non-reproducible. Update the dependencies in the package.json for lamatic (and
react-markdown as noted in the review) to fixed, pinned versions instead of
latest so fresh installs always resolve to the same package versions.
| <a href="https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits%2Fphishing-triage%2Fapps&env=PHISHING_TRIAGE,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY" target="_blank" style="text-decoration:none;"> | ||
| <div align="right"> | ||
| <span style="display:inline-block;background:#e63946;color:#fff;border-radius:6px;padding:10px 22px;font-size:16px;font-weight:bold;letter-spacing:0.5px;text-align:center;box-shadow:0 2px 8px 0 #0001;">Deploy on Vercel</span> | ||
| </div> | ||
| </a> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Add rel="noopener noreferrer" to the deploy link.
Opening an external page in a new tab without it leaves a tab-nabbing surface.
🔧 Suggested fix
-<a href="https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits%2Fphishing-triage%2Fapps&env=PHISHING_TRIAGE,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY" target="_blank" style="text-decoration:none;">
+<a href="https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits%2Fphishing-triage%2Fapps&env=PHISHING_TRIAGE,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY" target="_blank" rel="noopener noreferrer" style="text-decoration:none;">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <a href="https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits%2Fphishing-triage%2Fapps&env=PHISHING_TRIAGE,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY" target="_blank" style="text-decoration:none;"> | |
| <div align="right"> | |
| <span style="display:inline-block;background:#e63946;color:#fff;border-radius:6px;padding:10px 22px;font-size:16px;font-weight:bold;letter-spacing:0.5px;text-align:center;box-shadow:0 2px 8px 0 #0001;">Deploy on Vercel</span> | |
| </div> | |
| </a> | |
| <a href="https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits%2Fphishing-triage%2Fapps&env=PHISHING_TRIAGE,LAMATIC_API_URL,LAMATIC_PROJECT_ID,LAMATIC_API_KEY" target="_blank" rel="noopener noreferrer" style="text-decoration:none;"> | |
| <div align="right"> | |
| <span style="display:inline-block;background:`#e63946`;color:`#fff`;border-radius:6px;padding:10px 22px;font-size:16px;font-weight:bold;letter-spacing:0.5px;text-align:center;box-shadow:0 2px 8px 0 `#0001`;">Deploy on Vercel</span> | |
| </div> | |
| </a> |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kits/phishing-triage/README.md` around lines 1 - 5, The deploy anchor in the
phishing-triage README opens a new tab but is missing the required rel
attributes, creating a tab-nabbing risk. Update the external link that uses
target="_blank" to include rel="noopener noreferrer" so the Vercel deploy button
remains safe when opened from the README.
|
Hi @SharvikS! 👋 Before this PR can be reviewed by maintainers, please resolve all comments and requested changes from the CodeRabbit automated review. Steps to follow:
This helps keep the review process efficient for everyone. Thank you! 🙏 |
PR Checklist
1. Select Contribution Type
kits/<category>/<kit-name>/)bundles/<bundle-name>/)templates/<template-name>/)2. General Requirements
kebab-caseand matches the flow IDREADME.md(purpose, setup, usage)3. File Structure (Check what applies)
config.jsonpresent with valid metadata (name, description, tags, steps, author, env keys)flows/<flow-name>/(where applicable) include:config.json(Lamatic flow export)inputs.jsonmeta.jsonREADME.md.env.examplewith placeholder values only (kits only)config.jsonnode graphs (changes via Lamatic Studio export)4. Validation
npm install && npm run devworks locally (kits: UI runs; bundles/templates: flows are valid)[kit] Add <name> for <use case>)phishing-triagekit with kit metadata, deployment links, constitution, prompts, model config, and Lamatic flow definitions.triggerNodefor GraphQL/API inputLLMNodeto analyze phishing riskgraphqlResponseNodeto return the finalanswerapps/components/uiandapps/hooks, covering forms, dialogs, menus, tables, charts, navigation, sidebar, toast, and other shared controls used by the app.